
/**
 * 信息流程：三个部分
 * listen --> recognize --> dispatch
 * 监听、识别、重新分发
 */

// 封装手势操作
// new Listener(new Recognizer(dispatch))

// 获取元素
// let element = document.documentElement;
// let handler;
// let startX, startY;
// let isPan = false, isTap = true, isPress = false;

export function enableGesture(element) { // 启动
  new Listener(element, new Recognizer(new Dispatcher(element)))
}

export class Listener {
  constructor(element, recognizer) {
    let contexts = new Map();
    let isListeningMouse = false;

    // 监听鼠标操作
    element.addEventListener('mousedown', event => {
      let context = Object.create(null);
      // 根据不同的鼠标事件设置context, event.button(0: 左键 1: 中键  2: 右键)
      // 使用移位运算 <<： 获得1 2 4 8 16
      contexts.set('mouse' + (1 << event.button), context);
      // console.log('start', (1 << event.button));
      recognizer.start(event, context);

      let mousemove = event => {
        // 鼠标可以按键移动、直接移动、悬空移动
        // event.buttons === 0b11111 掩码(二进制):表示按键全按下来了
        let button = 1;

        while(button <= event.buttons) {
          if(button && event.buttons) {
            // 中键与右键相反 order of buttons & button property is not same
            let key = button === 2? 4: button === 4? 2: button;
            let context = contexts.get('mouse' + key);
            // console.log('move', button);
            recognizer.move(event, context);
          }
          button = button << 1; // 循环掩码
        }
      }

      let mouseup = event => {
        let context = contexts.get('mouse' + (1 << event.button));

        recognizer.end(event, context);
        contexts.delete('mouse' + (1 << event.button));
        
        if (event.buttons === 0) {  
          document.removeEventListener('mousemove', mousemove);
          document.removeEventListener('mouseup', mouseup);
          isListeningMouse = false;
        }
      }

      if(!isListeningMouse) { // 避免重复监听 
        document.addEventListener('mousemove', mousemove);
        document.addEventListener('mouseup', mouseup);
        isListeningMouse = true;
      }
    })

    // 监听触屏操作
    element.addEventListener('touchstart', event => {
      // 多点触碰所以是个数组 有唯一标识符 identifier 知道是哪一个点在move
      // console.log(event.changedTouches);
      for (let touch of event.changedTouches) {
        let context = Object.create(null);
        contexts.set(touch.identifier, context);
        recognizer.start(touch, context);
      }
    })
    element.addEventListener('touchmove', event => {
      for (let touch of event.changedTouches) {
        let context = contexts.get(touch.identifier);
        recognizer.move(touch, context);
      }
    })
    element.addEventListener('touchend', event => {
      for (let touch of event.changedTouches) {
        let context = contexts.get(touch.identifier);
        recognizer.end(touch, context);
        contexts.delete(touch.identifier); // 结束时需要清除
      }
    })
    element.addEventListener('touchcancel', event => { // 触摸，异常(被打断)结束
      for (let touch of event.changedTouches) {
        let context = contexts.get(touch.identifier);
        recognizer.cancel(touch, context);
        contexts.delete(touch.identifier);
      }
    })

  }
}

export class Recognizer {
  constructor(dispatcher) {
    this.dispatcher = dispatcher
  }

  /**
   * 抽取对应事件:
   * @param {*} point 
   * @param {*} context 
   * 区分手势
   * tap: 一次点击事件
   * pan: 比较缓慢的触点的推移(移动)
   * press: 长按(按压)
   */

  start(point, context) {
    // console.log('start', point.clientX, point.clientY);
    // 初始坐标  , 表示这行代码比较关系紧密
    context.startX = point.clientX, context.startY = point.clientY;

    this.dispatcher.dispatch('start', {
      clientX: point.clientX,
      clientY: point.clientY
    });

    // 存储当前点击的坐标、时间
    context.points = [{
      t: Date.now(),
      x: point.clientX,
      y: point.clientY
    }]
    
    context.isTap = true;
    context.isPan = false;
    context.isPress = false;

    context.handler = setTimeout(() => { // 超过半秒则视为按压
      context.isTap = false;
      context.isPan = false;
      context.isPress = true;
      context.handler = null; // 避免多次clear防止异常问题
      // console.log('press');
      this.dispatcher.dispatch('press', {});
    }, 500)
  }

  move(point, context) {
    // console.log('move', point.clientX, point.clientY);
    // 移动的距离 d： distance
    let dx = point.clientX - context.startX, dy = point.clientY - context.startY;

    if ( !context.isPan && dx ** 2 + dy ** 2 > 100 ) { // 移动10px(实别上允许的误差)
      context.isTap = false;
      context.isPan = true;
      context.isPress = false;
      context.isVertical = Math.abs(dx) < Math.abs(dy) // 区分上下还是左右滑动
      // console.log('panstart');
      this.dispatcher.dispatch('panstart', {
        startX: context.startX,
        startY: context.startY,
        clientX: point.clientX,
        clientY: point.clientY,
        isVertical: context.isVertical
      });
      clearTimeout(context.handler);
    }

    if (context.isPan) { // pan事件触发
      // console.log('dx: '+ dx,'dy: ' + dy);
      // console.log('pan');
      this.dispatcher.dispatch('pan', {
        startX: context.startX,
        startY: context.startY,
        clientX: point.clientX,
        clientY: point.clientY,
        isVertical: context.isVertical
      });
    }

    // 只取半秒内的点，来计算平均速度
    context.points = context.points.filter(item => Date.now() - item.t < 500)

    context.points.push({
      t: Date.now(),
      x: point.clientX,
      y: point.clientY
    })

  }

  end(point, context) {
    // console.log('end', point.clientX, point.clientY);
    if(context.isTap) { // 既没有发生定时 也没有发生move
      // console.log('tap')
      this.dispatcher.dispatch('tap', {});
      clearTimeout(context.handler);
    }

    if(context.isPress) {
      // console.log('pressend')
      this.dispatcher.dispatch('pressend', {});
    }
    context.points = context.points.filter(item => Date.now() - item.t < 500)
    // 计算离开(结束时鼠标滑动)速度
    let d, v;
    if (!context.points.length) {
      v = 0;
    } else {
      d = Math.sqrt((point.clientX - context.points[0].x) ** 2 +
        (point.clientY - context.points[0].y) ** 2);
      v = d / (Date.now() - context.points[0].t)
    }

    // console.log(v);
    if (v > 1.5) {
      // console.log('flick');
      this.dispatcher.dispatch('flick', {
        startX: context.startX,
        startY: context.startY,
        clientX: point.clientX,
        clientY: point.clientY,
        isVertical: context.isVertical,
        isFlick: context.isFlick,
        velocity: v
      });
      context.isFlick = true;
    } else {
      context.isFlick = false;
    }

    if(context.isPan) { // 要区分是否是flick
      // console.log('panend')
      this.dispatcher.dispatch('panend', {
        startX: context.startX,
        startY: context.startY,
        clientX: point.clientX,
        clientY: point.clientY,
        isVertical: context.isVertical,
        isFlick: context.isFlick,
        velocity: v
      });
    }
    this.dispatcher.dispatch('end', {
      startX: context.startX,
      startY: context.startY,
      clientX: point.clientX,
      clientY: point.clientY,
      isVertical: context.isVertical,
      isFlick: context.isFlick,
      velocity: v
    });
  }

  cancel(point, context) {
    // console.log('cancel', point.clientX, point.clientY);
    clearTimeout(context.handler);
    this.dispatcher.dispatch('cancel', {});
  }
  
}

export class Dispatcher {
  constructor(element) {
    this.element = element;
  }

  dispatch(type, properties) { // 派发事件
    let event = new Event(type);
    // console.log(event)
    for (let name in properties) {
      event[name] = properties[name];
    }
    this.element.dispatchEvent(event);
  }
}
